iT邦幫忙

2022 iThome 鐵人賽

DAY 10
3

Day 10 MongoDB 儲存後端

昨天我們實作了一個簡單的儲存後端,但是這個後端只是用 Map 來儲存資料,所以當機器人關閉時,所有資料都會消失。

今天我們來實作一個使用 MongoDB 的儲存後端,這樣就可以讓資料持久化了。

MongoStore

與 In-Mem 儲存後端一樣,我們也用一個 MongoStore 來實作 Store 介面(具有 .ctx 方法即可)。

不過,我們的 MongoStore 會擁有一個 MongoClient 以及一個 Db,讓我們操作資料庫。

一樣,我們使用一個 Proxy 把資料包起來,這樣在對其直接賦值時,就會自動更新資料庫了。(注意:目前的方法並沒有處理巢狀更新的部分,所以只有對物件 Top Level 賦值才會觸發更新)

export class MongoStore implements Store {
    private mongo: MongoClient;
    private db: Db;

    constructor(url: string, db = "pure-cat") {
        this.mongo = new MongoClient(url);
        this.db = this.mongo.db(db);
        this.connect();
    }

    private async connect() {
        // ... connect to the database
    }

    private async proxy<T>(
        type: "guilds" | "channels" | "users" | "global",
        key: string,
    ): Promise<T> {
        const collection = this.db.collection(type);
        const doc = await collection.findOne({ _id: key });
        const data = doc ? doc.data : {};

        return new Proxy(data, {
            set: (target, prop, value) => {
                if (typeof prop === "string") {
                    target[prop] = value;
                    collection.updateOne(
                        { _id: key },
                        { $set: { data: target } },
                        { upsert: true },
                    );
                    return true;
                } else {
                    return false;
                }
            },
            get: (target, prop) => {
                if (typeof prop === "string") {
                    return target[prop];
                } else {
                    return undefined;
                }
            },
        }) as T;
    }

    ctx(...args: ClientEvents[keyof ClientEvents]): StoreContext {
        const self = this;
        const resolved = store_resolver(args);

        const context: StoreContext = {
            async guild() {
                return resolved.guild ? self.proxy("guilds", resolved.guild) : undefined;
            },
            async channel() {
                return resolved.channel ? self.proxy("channels", resolved.channel) : undefined;
            },
            async user() {
                return resolved.user ? self.proxy("users", resolved.user) : undefined;
            },
            async global<T>(key: string) {
                return self.proxy("global", key) as T | undefined;
            },
        };

        return context;
    }
}

BTW,我把 store_resolverMemStore 獨立出來了,所以可以直接用。

使用不同的儲存後端

我們的 Bot 預設使用 MemStore,但是我們可以用 .store 方法來設定使用不同的儲存後端(就像用 .use 使用模組一樣,但後端只能有一個)。

import { MongoStore } from "pure-cat-store-mongo";

load_env_recursively();

new Bot({ load_env: false })
    .store(new MongoStore(process.env.MONGODB || ""))
    .use(new Echo())
    .start()
    .then(() => console.log("Bot started!"));

load_env_recursively 會去讀取 .env 檔案,並且把環境變數載入進來。

使用不同後端時,因為介面都一樣,所以其他的模組不需要做任何修改。

所以我們可以用昨天的 WordCount 模組來測試一下。

https://i.imgur.com/qw3V0sm.png

Mongo Compass

我們也可以用 Mongo Compass 來查看資料庫的內容。它是一個 GUI 的資料庫管理工具,可以讓我們方便的查看內容、執行指令或監測效能。

https://i.imgur.com/5VJ2Swx.png

https://i.imgur.com/7EhE0c7.png


每日鐵人賽熱門 Top 10 (2022-09-25)

以 2022/09/24 20:00 ~ 2022/09/25 20:00 文章觀看數增加值排名

誤差: 1 小時

  1. +462 D07 - 開趴前先 loading 一下
    • 作者: 鱈魚
    • 系列:派對動物嗨起來!
  2. +442 D09 - NestJS 是啥?好吃嗎?
    • 作者: 鱈魚
    • 系列:派對動物嗨起來!
  3. +440 D08 - 載入就應該要有載入的樣子
    • 作者: 鱈魚
    • 系列:派對動物嗨起來!
  4. +410 Day02 - 因 Node 而存在的 npm
    • 作者: rocketpencil
    • 系列:因為拖延症而沒有好好準備有系統性文章架構的我只能靠一天一筆記來贖罪
  5. +387 D10 - 讓前後端接上線
    • 作者: 鱈魚
    • 系列:派對動物嗨起來!
  6. +339 「全端挑戰」製作動態網站第一步從了解useState與它的用法開始
    • 作者: Ko
    • 系列:自己做一個價值幾十萬的動態網站,學會Mern開發、前台UI設計各式觀念與各式Lib、typescript你該學會的前端技術
  7. +298 「全端挑戰」Scss與React Component的動態實作Navbar與Header
    • 作者: Ko
    • 系列:自己做一個價值幾十萬的動態網站,學會Mern開發、前台UI設計各式觀念與各式Lib、typescript你該學會的前端技術
  8. +290 「全端挑戰」了解Scss與React Component與首頁概念圖與UI實作
    • 作者: Ko
    • 系列:自己做一個價值幾十萬的動態網站,學會Mern開發、前台UI設計各式觀念與各式Lib、typescript你該學會的前端技術
  9. +289 Day01 - 為什麼要裝 Node.js?
    • 作者: rocketpencil
    • 系列:因為拖延症而沒有好好準備有系統性文章架構的我只能靠一天一筆記來贖罪
  10. +289 「全端挑戰」使用useState製作彈跳視窗、製作Calendar與各種互動介面
    • 作者: Ko
    • 系列:自己做一個價值幾十萬的動態網站,學會Mern開發、前台UI設計各式觀念與各式Lib、typescript你該學會的前端技術

看了一下 Rank 9 的那篇,正好讓我想起最近看到同學上 WebGL 的時候用了 var 來宣告變數。請不要用 var,它就像是 Python 的變數一樣存取範圍會亂飛,甚至還會直接掛到 globalThis 上,這樣容易造成很多莫名其妙的問題,請使用 letconst 來宣告變數。


上一篇
Day 9 實作 In-Mem 儲存系統(以及 Word Count 模組)
下一篇
Day 11 Server Subscription Plans
系列文
Discord Bot with TypeScript: Framework, Database, and Modules30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言